void
printf(const char *format, ...);
char *
remap(char *a, int b);
void
outl(unsigned int s, unsigned int pnum);
void
outw(unsigned short s, unsigned int pnum);
void
outb(unsigned char s, unsigned int pnum);
unsigned int
inl(unsigned int pnum);
unsigned short
inw(unsigned int pnum);
unsigned char
inb(unsigned int pnum);

/** Simple PCI device description */
struct pci_dev
{
  /** Header */
  union
  {
    struct
    {
      unsigned short vendor_id;
      unsigned short dev_id;
    } struct_r0;
    unsigned int raw_r0;
  } r0;

  union
  {
    struct
    {
      unsigned short cmd;
      unsigned short status;
    } struct_r1;
    unsigned int raw_r1;
  } r1;
  union
  {
    struct
    {
      unsigned char rev_id;
      unsigned char prog_if;
      unsigned char subclass;
      unsigned char classcode;
    } struct_r2;
    unsigned int raw_r2;
  } r2;
  union
  {
    struct
    {
      unsigned char cachesiz;
      unsigned char latency;
      unsigned char type;
      unsigned char BIST;
    } struct_r3;
    unsigned int raw_r3;
  } r3;

  unsigned int bar[6];
  unsigned int cardbus;
  unsigned int subsys;
  unsigned int rombar;
  unsigned int capabilities;
  unsigned int reserved;
  union
  {
    struct
    {
      unsigned char irq;
      unsigned char pin;
      unsigned char min_grant;
      unsigned char max_latency;
    } irq_info;
    unsigned int irq_reg;
  } interrupts;

  /* bus device number */
  int bus_num;
  /* device number on that bus */
  int dev_num;
  int func_num;

  /* Memory in IO address space. Can be accsessed directly: inb, outb... */
  unsigned long io_mem;
  /* Mapped memory (not working right now) */
  unsigned long map_mem;

  /* Device 'body' */
  void *irq_handler;
  /* private device data */
  void *priv;
};

/** PCI Configuration Space Address */
#define	PCI_CONF_ADR	0x0CF8
/** PCI Configuration Space Data */
#define	PCI_CONF_DATA	0x0CFC

#define PCI_DEV_MAX_FUNCS	8
#define PCI_CONF_HDR_MULTIFUNC	0x80

/* PCI Configuration Address Space Layout (parts) */
#define PCI_COMMAND		0x04	/* 16 bits */
#define PCI_COMMAND_MASTER	0x4	/* Enable bus mastering */

#define	PCI_NODEV	0xFFFF

#define PCI_CONF_HDR_DEVREG		0x0
#define PCI_CONF_HDR_CMDREG		0x4
#define PCI_CONF_HDR_CLASSREG		0x8
#define PCI_CONF_HDR_TYPEREG		0xC

#define PCI_CONF_BAR_MAX	6
#define PCI_CONF_HDR_BAR0	0x10
#define PCI_CONF_HDR_BAR1 	0x14
#define PCI_CONF_HDR_BAR2 	0x18
#define PCI_CONF_HDR_BAR3 	0x1C
#define PCI_CONF_HDR_BAR4 	0x20
#define PCI_CONF_HDR_BAR5 	0x24
#define PCI_CONF_HDR_IRQ	0x3C

#define ATA_PRIMARY_CMD_IO	0x1F0
#define ATA_PRIMARY_CTRL_IO	0x3F4
#define ATA_SECNDRY_CMD_IO	0x170
#define ATA_SECNDRY_CTRL_IO	0x374

#define ATA_DATA_REG		0x0
#define ATA_ERROR_REG		0x1
#define ATA_SECT_COUNT_REG	0x2
#define ATA_SECT_NUM_REG	0x3
#define ATA_CYL_LOW_REG		0x4
#define ATA_CYL_HIGH_REG	0x5
#define ATA_DRIVE_SEL_REG	0x6
#define ATA_STAT_CMD_REG	0x7

#define PII_BMICOM1	0x0		// command register
#define PII_BMISTA1	0x2		// status register
#define PII_BMIDTP1	0x0		// Descriptor pointer register
static struct pci_dev pdevs[0x400];
static unsigned int iobase;

static void
setMaster(struct pci_dev *pdev)
{
  int busdev;
  if (!pdev)
    return;
  busdev = (pdev->bus_num << 16) | (pdev->dev_num << 11) | (1 << 31);
  outl(busdev + PCI_COMMAND, (int) iobase + PCI_CONF_ADR);
  outw(PCI_COMMAND_MASTER, (int) iobase + PCI_CONF_ADR);
}

struct dma_descriptor
{

};

static void
detectIde(struct pci_dev *pdev)
{
  int i;
  char data[2];
  int d_buffer[128];
  unsigned char seccnt;

  if (!pdev)
    return;

  pdev->bar[0] = pdev->bar[0] ? pdev->bar[0] : ATA_PRIMARY_CMD_IO;
  pdev->bar[1] = pdev->bar[1] ? pdev->bar[1] : ATA_PRIMARY_CTRL_IO;
  pdev->bar[2] = pdev->bar[2] ? pdev->bar[2] : ATA_SECNDRY_CMD_IO;
  pdev->bar[3] = pdev->bar[3] ? pdev->bar[3] : ATA_SECNDRY_CTRL_IO;

  if (!iobase)
    {
      printf("Invalid IOBASE!!!!!!!!!!!\n");
      return;
    }

  setMaster(pdev);

  seccnt = 1;

  /* select 0 drive LBA mode */
  outb(0xE0, pdev->bar[0] + ATA_DRIVE_SEL_REG);
  /* set sector count */
  outb(seccnt, pdev->bar[0] + ATA_SECT_COUNT_REG);
  /* set starting sector number */
  outb(0, pdev->bar[0] + ATA_SECT_NUM_REG);
  /* set cylinder (low byte) */
  outb(0, pdev->bar[0] + ATA_CYL_LOW_REG);
  /* set cylinder (high byte) */
  outb(0, pdev->bar[0] + ATA_CYL_HIGH_REG);
  /* set command */
  outb(0x20, pdev->bar[0] + ATA_STAT_CMD_REG);

  while (!(inb(pdev->bar[0] + ATA_STAT_CMD_REG) & 8))
    ;

  for (i = 0; i < (seccnt * 512) / 2; i++)
    {
      *(unsigned short *) data = inw(pdev->bar[0] + ATA_DATA_REG);
      putchar(data[0]);
      putchar(data[1]);
    }

  return;
}

/**
 *
 */
int
main(void *data)
{
  int busdev;
  int busnum;
  int devnum;
  int barnum;
  int pcidat;
  int header;
  int vendor;
  int device;
  int fun, funcs = 1;
  int busdevfun;
  struct pci_dev pdev;
//  int *qqq = (int *) 0x23482;

  iobase = (unsigned int) remap((unsigned int) 0x0, (int) 0x1000);

  for (busnum = 0; busnum < (1 << 8); busnum++)
    {

      for (devnum = 0; devnum < (1 << 5); devnum++)
        {

          /* FIXME buffer overflow ? */
          busdev = (busnum << 16) | (devnum << 11) | (1 << 31);
          outl(busdev, (iobase + PCI_CONF_ADR));
          pcidat = inl(iobase + PCI_CONF_DATA);

          vendor = pcidat & 0x0000FFFF;
          if (vendor == PCI_NODEV)
            continue;

          device = pcidat >> 16;
          outl(busdev | PCI_CONF_HDR_TYPEREG, (iobase + PCI_CONF_ADR));
          header = (inl((iobase + PCI_CONF_DATA)) >> 16) & 0xFF;

          /*
           * Multifunction device!
           */
          if (header & PCI_CONF_HDR_MULTIFUNC)
            {
              funcs = PCI_DEV_MAX_FUNCS;
            }

          /*
           * Enumerate each function
           */
          for (fun = 0; fun < funcs; fun++)
            {

              busdevfun = busdev | (fun << 8);
              outl(busdevfun, (iobase + PCI_CONF_ADR));
              pdev.r0.raw_r0 = (unsigned int) inl((iobase + PCI_CONF_DATA));

              if (pdev.r0.struct_r0.vendor_id == PCI_NODEV)
                continue;

              pdev.bus_num = busnum;
              pdev.dev_num = devnum;
              pdev.func_num = fun;

              /* read device class */
              outl((busdevfun | PCI_CONF_HDR_CLASSREG),
                  (iobase + PCI_CONF_ADR));
              pdev.r2.raw_r2 = inl(iobase + PCI_CONF_DATA);

              /* Read IRQ */
              outl((busdevfun | PCI_CONF_HDR_IRQ), (iobase + PCI_CONF_ADR));
              pdev.interrupts.irq_reg = inl(iobase + PCI_CONF_DATA);

              /* Print out device info */
              printf("%d:%d.%d Vend[0x%x] Dev[0x%x] DevClass[0x%x] [rev.%d]\n",
                  busnum, devnum, fun, pdev.r0.struct_r0.vendor_id,
                  pdev.r0.struct_r0.dev_id, pdev.r2.struct_r2.classcode,
                  pdev.r2.struct_r2.rev_id);

              /* read bars */
              for (barnum = 0; barnum < PCI_CONF_BAR_MAX; barnum++)
                {
                  outl(busdevfun | (PCI_CONF_HDR_BAR0 + (barnum << 2)),
                      (iobase + PCI_CONF_ADR));
                  pdev.bar[barnum] = inl(iobase + PCI_CONF_DATA);
                  printf("BAR=0x%x\n", pdev.bar[barnum]);
                }
              if (pdev.r2.struct_r2.classcode == 0x1)
                detectIde(&pdev);
              // ndevs++;
            }
        }
    }

//  *qqq = 0x44556677;
//
//  busdevfun = *qqq;
//
//  printf("Reading wrote memory = 0x%x\n", *qqq);

  printf("PCI scan finished\n");

  for (;;)
    {
      printf("PCI...\n");
    }

  return 0;
}
